﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Reflection;
using Kokomo.Mirror.CliMetadata;
using System.IO;
using Kokomo.Mirror.Test.Models;
using System.Runtime.CompilerServices;

namespace Kokomo.Mirror.Test
{
	/// <summary>
	/// Summary description for ReflectionTest
	/// </summary>
	[TestClass]
	public class ReflectionTest
	{
		public ReflectionTest()
		{
			//
			// TODO: Add constructor logic here
			//
		}

		private TestContext testContextInstance;

		/// <summary>
		///Gets or sets the test context which provides
		///information about and functionality for the current test run.
		///</summary>
		public TestContext TestContext
		{
			get
			{
				return testContextInstance;
			}
			set
			{
				testContextInstance = value;
			}
		}

		#region Additional test attributes
		//
		// You can use the following additional attributes as you write your tests:
		//
		// Use ClassInitialize to run code before running the first test in the class
		// [ClassInitialize()]
		// public static void MyClassInitialize(TestContext testContext) { }
		//
		// Use ClassCleanup to run code after all tests in a class have run
		// [ClassCleanup()]
		// public static void MyClassCleanup() { }
		//
		// Use TestInitialize to run code before running each test 
		// [TestInitialize()]
		// public void MyTestInitialize() { }
		//
		// Use TestCleanup to run code after each test has run
		// [TestCleanup()]
		// public void MyTestCleanup() { }
		//
		#endregion

		#region Assembly tests
		public void CompareAssemblies(
			Assembly reflectionAssembly,
			Assembly mirrorAssembly
			)
		{
			Assert.AreEqual(reflectionAssembly.CodeBase, mirrorAssembly.CodeBase, true);
			Assert.AreEqual(reflectionAssembly.EntryPoint, mirrorAssembly.EntryPoint);
			Assert.AreEqual(reflectionAssembly.EscapedCodeBase, mirrorAssembly.EscapedCodeBase, true);
			// Evidence
			Assert.AreEqual(reflectionAssembly.FullName, mirrorAssembly.FullName);
			//Assert.AreEqual(reflectionAssembly.GlobalAssemblyCache, mirrorAssembly.GlobalAssemblyCache);
			// HostContext
			Assert.AreEqual(reflectionAssembly.ImageRuntimeVersion, mirrorAssembly.ImageRuntimeVersion);
			Assert.AreEqual(reflectionAssembly.IsDynamic, mirrorAssembly.IsDynamic);
			// IsFullyTrusted
			Assert.AreEqual(reflectionAssembly.Location, mirrorAssembly.Location);
			//Assert.AreEqual(reflectionAssembly.ManifestModule, mirrorAssembly.ManifestModule);
			// PermissionSet
			// ReflectionOnly
			// SecurityRuleSet
			// CreateInstance
			// CreateQualifiedName
			// Equals
			// GetCustomAttributes
			// GetCustomAttributesData
			// GetExportedTypes
			// GetFile
			// GetFiles
			// GetLoadedModules
			// GetManifestResourceInfo
			// GetManifestResourceNames
			// GetManifestResourceStream
			// GetModule
			// GetModules
			// GetName
			// GetReferencedAssemblies
			// GetSatelliteAssembly
			// GetType
			// GetTypes
			// IsDefined
			// LoadModule
			Assert.AreEqual(reflectionAssembly.ToString(), mirrorAssembly.ToString());
		}

		public Assembly GetMirrorAssembly(
			Assembly reflectionAssembly
			)
		{
			return reflectionAssembly.Mirror();
		}

		[TestMethod]
		public void GacAssemblyTest()
		{
			Assembly reflectionAssembly = typeof(object).Assembly;
			Assembly mirrorAssembly = GetMirrorAssembly(reflectionAssembly);
			CompareAssemblies(reflectionAssembly, mirrorAssembly);
		}

		[TestMethod]
		public void UserAssemblyTest()
		{
			Assembly reflectionAssembly = Assembly.GetExecutingAssembly();
			Assembly mirrorAssembly = GetMirrorAssembly(reflectionAssembly);
			CompareAssemblies(reflectionAssembly, mirrorAssembly);
		}
		#endregion

		#region Module tests
		public void CompareModules(
			Module reflectionModule,
			Module mirrorModule
			)
		{
			Assert.AreEqual(reflectionModule.FullyQualifiedName, mirrorModule.FullyQualifiedName);
			Assert.AreEqual(reflectionModule.MDStreamVersion, mirrorModule.MDStreamVersion);
			Assert.AreEqual(reflectionModule.MetadataToken, mirrorModule.MetadataToken);
			// ModuleHandle
			Assert.AreEqual(reflectionModule.ModuleVersionId, mirrorModule.ModuleVersionId);
			Assert.AreEqual(reflectionModule.Name, mirrorModule.Name);
			Assert.AreEqual(reflectionModule.ScopeName, mirrorModule.ScopeName);
			// FindTypes
			// GetCustomAttributes
			// GetCustomAttributeData
			// GetField
			// GetFields
			// GetMethod
			// GetMethods

			PortableExecutableKinds reflectionKind, mirrorKind;
			ImageFileMachine reflectionMachine, mirrorMachine;
			reflectionModule.GetPEKind(out reflectionKind, out reflectionMachine);
			mirrorModule.GetPEKind(out mirrorKind, out mirrorMachine);
			Assert.AreEqual(reflectionKind, mirrorKind);
			Assert.AreEqual(reflectionMachine, mirrorMachine);

			// GetSignerCertificate
			// GetType
			// GetTypes
			// IsDefined
			// IsResource
			// ResolveField
			// ResolveMember
			// ResolveMethod
			// ResolveSignature
			// ResolveString
			// ResolveType
			Assert.AreEqual(reflectionModule.ToString(), mirrorModule.ToString());
		}

		[TestMethod]
		public void UserModuleTest()
		{
			Assembly reflectionAssembly = Assembly.GetExecutingAssembly();
			Module reflectionModule = reflectionAssembly.ManifestModule;
			Module mirrorModule = reflectionModule.Mirror();
			CompareModules(reflectionModule, mirrorModule);
		}
		#endregion

		public bool CompareMembers(
			MemberInfo reflectedMember,
			MemberInfo mirroredMember,
			bool deep = true
			)
		{
			Assert.AreEqual(reflectedMember.MetadataToken, mirroredMember.MetadataToken);
			Assert.AreEqual(reflectedMember.Name, mirroredMember.Name);
			Assert.AreEqual(reflectedMember.MemberType, mirroredMember.MemberType);
			if (reflectedMember.DeclaringType != null)
				Assert.IsTrue(CompareMembers(reflectedMember.DeclaringType, mirroredMember.DeclaringType, false));
			else
				Assert.IsNull(mirroredMember.DeclaringType);
			if (reflectedMember.ReflectedType != null)
				Assert.IsTrue(CompareMembers(reflectedMember.ReflectedType, mirroredMember.ReflectedType, false));
			else
				Assert.IsNull(mirroredMember.ReflectedType);

			if (deep)
			{
				Type type = reflectedMember as Type;
				if (type != null)
					CompareTypes(type, (Type)mirroredMember);
			}

			return true;
		}

		#region Type tests
		public void CompareTypes(
			Type reflectedType,
			Type mirroredType,
			bool compareMembers = true
			)
		{
			Assert.AreEqual(reflectedType.AssemblyQualifiedName, mirroredType.AssemblyQualifiedName);
			Assert.AreEqual(reflectedType.Attributes, mirroredType.Attributes);
			if (reflectedType.BaseType == null)
				Assert.IsNull(mirroredType.BaseType);
			else
				Assert.AreEqual(reflectedType.BaseType.FullName, mirroredType.BaseType.FullName);
			Assert.AreEqual(reflectedType.ContainsGenericParameters, mirroredType.ContainsGenericParameters);
			Assert.AreEqual(reflectedType.FullName, mirroredType.FullName);
			// UNDONE: Not sure how this should behave
			// Assert.AreEqual(reflectedType.GUID, mirroredType.GUID);
			Assert.AreEqual(reflectedType.HasElementType, mirroredType.HasElementType);
			if (reflectedType.HasElementType)
				Assert.AreEqual(reflectedType.GetElementType().FullName, mirroredType.GetElementType().FullName);
			else
				Assert.IsNull(mirroredType.GetElementType());
			Assert.AreEqual(reflectedType.IsAbstract, mirroredType.IsAbstract);
			Assert.AreEqual(reflectedType.IsAnsiClass, mirroredType.IsAnsiClass);
			Assert.AreEqual(reflectedType.IsAutoClass, mirroredType.IsAutoClass);
			Assert.AreEqual(reflectedType.IsAutoLayout, mirroredType.IsAutoLayout);
			Assert.AreEqual(reflectedType.IsByRef, mirroredType.IsByRef);
			Assert.AreEqual(reflectedType.IsClass, mirroredType.IsClass);
			Assert.AreEqual(reflectedType.IsCOMObject, mirroredType.IsCOMObject);
			Assert.AreEqual(reflectedType.IsEnum, mirroredType.IsEnum);
			if (reflectedType.IsEnum)
				CompareMembers(reflectedType.GetEnumUnderlyingType(), mirroredType.GetEnumUnderlyingType(), false);

			Assert.AreEqual(reflectedType.IsExplicitLayout, mirroredType.IsExplicitLayout);
			Assert.AreEqual(reflectedType.IsGenericParameter, mirroredType.IsGenericParameter);
			Assert.AreEqual(reflectedType.IsGenericType, mirroredType.IsGenericType);
			Assert.AreEqual(reflectedType.IsGenericTypeDefinition, mirroredType.IsGenericTypeDefinition);
			Assert.AreEqual(reflectedType.IsImport, mirroredType.IsImport);
			Assert.AreEqual(reflectedType.IsInterface, mirroredType.IsInterface);
			Assert.AreEqual(reflectedType.IsLayoutSequential, mirroredType.IsLayoutSequential);
			// UNDONE: Invokes GetUnderlyingType
			// Assert.AreEqual(reflectedType.IsContextful, mirroredType.IsContextful);
			// Assert.AreEqual(reflectedType.IsMarshalByRef, mirroredType.IsMarshalByRef);
			// Assert.AreEqual(reflectedType.IsSerializable, mirroredType.IsSerializable);
			Assert.AreEqual(reflectedType.IsNested, mirroredType.IsNested);
			Assert.AreEqual(reflectedType.IsNestedAssembly, mirroredType.IsNestedAssembly);
			Assert.AreEqual(reflectedType.IsNestedFamANDAssem, mirroredType.IsNestedFamANDAssem);
			Assert.AreEqual(reflectedType.IsNestedFamily, mirroredType.IsNestedFamily);
			Assert.AreEqual(reflectedType.IsNestedFamORAssem, mirroredType.IsNestedFamORAssem);
			Assert.AreEqual(reflectedType.IsNestedPrivate, mirroredType.IsNestedPrivate);
			Assert.AreEqual(reflectedType.IsNestedPublic, mirroredType.IsNestedPublic);
			Assert.AreEqual(reflectedType.IsNotPublic, mirroredType.IsNotPublic);
			Assert.AreEqual(reflectedType.IsPointer, mirroredType.IsPointer);
			Assert.AreEqual(reflectedType.IsPrimitive, mirroredType.IsPrimitive);
			Assert.AreEqual(reflectedType.IsPublic, mirroredType.IsPublic);
			Assert.AreEqual(reflectedType.IsSealed, mirroredType.IsSealed);
			Assert.AreEqual(reflectedType.IsSpecialName, mirroredType.IsSpecialName);
			Assert.AreEqual(reflectedType.IsUnicodeClass, mirroredType.IsUnicodeClass);
			Assert.AreEqual(reflectedType.IsValueType, mirroredType.IsValueType);
			Assert.AreEqual(reflectedType.IsVisible, mirroredType.IsVisible);
			Assert.AreEqual(reflectedType.Namespace, mirroredType.Namespace);
			//Assert.AreEqual(reflectedType.StructLayoutAttribute, mirroredType.StructLayoutAttribute);
			Assert.AreEqual(reflectedType.TypeInitializer, mirroredType.TypeInitializer);
			//Assert.AreEqual(reflectedType.UnderlyingSystemType, mirroredType.UnderlyingSystemType);
			// UNDONE: Unimplemented
			//Assert.AreEqual(reflectedType.IsSecurityCritical, mirroredType.IsSecurityCritical);
			//Assert.AreEqual(reflectedType.IsSecuritySafeCritical, mirroredType.IsSecuritySafeCritical);
			//Assert.AreEqual(reflectedType.IsSecurityTransparent, mirroredType.IsSecurityTransparent);

			Assert.AreEqual(reflectedType.IsArray, mirroredType.IsArray);
			if (reflectedType.IsArray)
			{
				Assert.AreEqual(reflectedType.GetArrayRank(), mirroredType.GetArrayRank());
			}
			Assert.AreEqual(reflectedType.IsGenericParameter, mirroredType.IsGenericParameter);
			if (reflectedType.IsGenericParameter)
			{
				Assert.AreEqual(reflectedType.DeclaringMethod, mirroredType.DeclaringMethod);
				Assert.AreEqual(reflectedType.GenericParameterAttributes, mirroredType.GenericParameterAttributes);
				Assert.AreEqual(reflectedType.GenericParameterPosition, mirroredType.GenericParameterPosition);
			}

			if (compareMembers)
			{
				MemberInfo[] reflectedMembers = reflectedType.GetMembers();
				MemberInfo[] mirroredMembers = mirroredType.GetMembers();
				IEqualityComparer<MemberInfo> comparer = new DelegatedEqualityComparer<MemberInfo>(
					(x, y) => CompareMembers(x, y)
					);
				Assert.IsTrue(reflectedMembers.SequenceEqual(mirroredMembers, comparer));
			}

			// TypeHandle
		}

		[TestMethod]
		public void SimpleTypeTest()
		{
			Type reflectedType = typeof(SimpleClass);
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType, mirroredType);
		}
		[TestMethod]
		public void NaturalEnumTest()
		{
			Type reflectedType = typeof(NaturalEnum);
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType, mirroredType);
		}
		[TestMethod]
		public void ByteEnumTest()
		{
			Type reflectedType = typeof(ByteEnum);
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType, mirroredType);
		}
		[TestMethod]
		public void SimpleStructTest()
		{
			Type reflectedType = typeof(SimpleStruct);
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType, mirroredType);
		}
		[TestMethod]
		public void SimpleByRefTypeTest()
		{
			Type reflectedType = typeof(SimpleClass).MakeByRefType();
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType, mirroredType);
		}
		[TestMethod]
		public void SimplePointerTypeTest()
		{
			Type reflectedType = typeof(SimpleClass).MakePointerType();
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType, mirroredType);
		}
		[TestMethod]
		public void SimpleArrayTypeTest()
		{
			Type reflectedType = typeof(SimpleClass).MakeArrayType();
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType, mirroredType);
		}
		[TestMethod]
		public void GenericTypeDefinitionTest()
		{
			Type reflectedType = typeof(GenericClass<,>);
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType, mirroredType);
		}
		[TestMethod]
		public void GenericTypeParamTest()
		{
			Type reflectedType = typeof(GenericClass<,>);
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType.GetGenericArguments()[0], mirroredType.GetGenericArguments()[0]);
		}
		[TestMethod]
		public void GenericTypeTest()
		{
			Type reflectedType = typeof(GenericClass<int, string>);
			Type mirroredType = reflectedType.Mirror();
			CompareMembers(reflectedType, mirroredType);
		}
		#endregion

		[TestMethod]
		public void SimpleMethodTest()
		{
			SimpleClass obj = new SimpleClass();
			MethodInfo method = ((Func<int>)obj.FatMethod).Method;
			MethodInfo mirroredMethod = method.Mirror();

			MethodBody reflectedBody = method.GetMethodBody();
			MethodBody mirroredBody = mirroredMethod.GetMethodBody();
		}
		[TestMethod]
		public void MethodImplCtorTest()
		{
			MethodBase method = typeof(MethodImplAttribute).GetConstructors()[0];// (new Type[] { typeof(MethodImplAttributes) });
			MethodBase mirroredMethod = method.Mirror();

			MethodBody reflectedBody = method.GetMethodBody();
			MethodBody mirroredBody = mirroredMethod.GetMethodBody();


		}

		[TestMethod]
		public void GenericArgTest()
		{
			Type valuePair = typeof(KeyValuePair<,>);
			Type listType = typeof(List<>);
			Type openListType = listType.MakeGenericType(valuePair);
			Type openArrayType = openListType.MakeArrayType();
			Type[] genericArgs = openArrayType.GetGenericArguments();
		}

		[TestMethod]
		public void GenericArgTest2()
		{
			Type t = typeof(GenericClass<,>.NestedGenericClass<>);
			Type[] genericArgs = t.GetGenericArguments();
			Type tMirrored = t.Mirror();
			Type[] mirroredGenericArgs = tMirrored.GetGenericArguments();

			Type tInst = typeof(GenericClass<int, int>.NestedGenericClass<int>);
			Type[] genericInstArgs = tInst.GetGenericArguments();
		}

		[TestMethod]
		public void GenericDefinitionTest()
		{
			Type t = typeof(List<>);
			Type definition = t.GetGenericTypeDefinition();
		}

		[TestMethod]
		public void GenericMethodTest()
		{
			List<int> list = new List<int>();
			MethodInfo addMethod = ((Action<int>)list.Add).Method;
			MethodInfo baseMethod = addMethod.GetGenericMethodDefinition();
		}
	}
}
